package com.oglvip.business.remote.support.proxy;

import com.oglvip.business.remote.support.exception.RestException;
import com.oglvip.business.remote.support.proxy.exchange.Exchanger;
import com.oglvip.business.remote.support.proxy.exchange.HttpClientExchanger;
import com.oglvip.business.remote.support.utils.ClassUtils;
import com.oglvip.business.remote.support.utils.MethodUtils;
import javassist.*;
import javassist.bytecode.SignatureAttribute;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

public class ProxyFactory {
    private static Logger _logger = LoggerFactory.getLogger(ProxyFactory.class);
    private static final String DEFAULT_RESOURCE = "hosts.properties";
    /// add by hanchao,2016-12-05
    // 暂时为了兼容spring beandefination加上的
    public static final String PROXY_SUFFIX = "ProxyImpl";
    /**
     * Map<Service,host>
     */
    private static Map<Class<?>, String> hosts = new ConcurrentHashMap<Class<?>, String>();
    /**
     * hosts mapping
     */
    private static Properties properties = new Properties();

    private int _classIndex = 0;

    /**
     * 兼容springRestTempalte
     */
    private static String SPRING_REFERENCE_NAME = "org.springframework.core.ParameterizedTypeReference";
    private static Map<String, Object> _springReference = new ConcurrentHashMap<String, Object>();

    /**
     * ObjectMapper typeFactory
     */
    private static String JACKSON_REFRENCE_NAME = "com.fasterxml.jackson.core.type.TypeReference";
    private static Map<String, Object> _jacksonReference = new ConcurrentHashMap<String, Object>();

    private static boolean springTemplateSupport = false;

    private Exchanger exchanger;

    static {
        try {
            Class.forName(SPRING_REFERENCE_NAME);
            springTemplateSupport = true;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void addHost(Class<?> clazz, String host) {
        hosts.put(clazz, host);
    }

    public static String getHost(Class<?> clazz) {
        String host = hosts.get(clazz);
        if (StringUtils.isNotBlank(host)) {
            return properties.getProperty(host);
        }
        throw new RestException("interface host must config!");
    }

    static void addSpringRefrence(String type, Object ref) {
        _springReference.put(type, ref);
    }

    public static Object getSpringRefrence(String type) {
        return _springReference.get(type);
    }

    static void addJacksonRefrence(String type, Object ref) {
        _jacksonReference.put(type, ref);
    }

    public static Object getJacksonSpringRefrence(String type) {
        return _jacksonReference.get(type);
    }

    public Object createProxy(Class<?> inter)
            throws InstantiationException, IllegalAccessException {

        Object obj = null;
        try {
            obj = impl(inter);
        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (CannotCompileException e) {
            e.printStackTrace();
        }
        Class<?> clazz = obj.getClass();
        // 这里可以不做实现类，obj可以为忽略也可以正常创建代理
        Object proxy = Proxy.newProxyInstance(inter.getClassLoader(),
                clazz.getInterfaces(), new RestProxyHandler(obj, exchanger));
        return proxy;

    }

    private static ClassPool cp;

    private Object impl(Class<?> inter) throws NotFoundException,
            InstantiationException, IllegalAccessException,
            CannotCompileException {


        if (cp == null) {
            synchronized (hosts) {
                if (cp == null) {
                    cp = new ClassPool();
                    cp.appendSystemPath();
                    cp.insertClassPath(new ClassClassPath(this.getClass()));
                }
            }
        }
//		cp.insertClassPath(inter.getName());
//		cp = ClassPool.getDefault();
        String interfaceName = inter.getName();
        // 要实现的接口的包名+接口名
        String interfaceNamePath = interfaceName;

        CtClass ctInterface = cp.getCtClass(interfaceNamePath);

        String implClass = interfaceNamePath + PROXY_SUFFIX;

        CtClass cc = cp.makeClass(implClass);
        cc.addInterface(ctInterface);

        Method[] methods = inter.getMethods();


        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            _logger.debug(" ====== " + method.getName());
            dynamicImplementsMethodsFromInterface(cc, method);

            //generic support

            String returnType = MethodUtils.getGenericReturnTypeName(method);
            if (method.getGenericReturnType() instanceof ParameterizedType) {
                if (springTemplateSupport) {
                    String _refClassName = "TypeReference" + _classIndex++;
                    CtClass _refClass = cp.makeClass(_refClassName, cp.get(SPRING_REFERENCE_NAME));

                    SignatureAttribute attribute = new SignatureAttribute(_refClass.getClassFile().getConstPool(), "L" + ClassUtils.getClassByteName(SPRING_REFERENCE_NAME) + "<L" + ClassUtils.convertType2Signature(returnType) + ";>;");
                    _refClass.setAttribute("Signature", attribute.get());
                    _refClass.addConstructor(CtNewConstructor.make("public " + _refClassName + "(){super();}\n", _refClass));
                    writeCtFile(_refClass);
                    this.addSpringRefrence(returnType, _refClass.toClass().newInstance());
                }
                //jackson type

                {
                    String _refClassName = "TypeReference" + _classIndex++;
                    CtClass _refClass = cp.makeClass(_refClassName, cp.get(JACKSON_REFRENCE_NAME));

                    SignatureAttribute attribute = new SignatureAttribute(_refClass.getClassFile().getConstPool(), "L" + ClassUtils.getClassByteName(JACKSON_REFRENCE_NAME) + "<L" + ClassUtils.convertType2Signature(returnType) + ";>;");
                    _refClass.setAttribute("Signature", attribute.get());
                    _refClass.addConstructor(CtNewConstructor.make("public " + _refClassName + "(){super();}\n", _refClass));
                    writeCtFile(_refClass);
                    this.addJacksonRefrence(returnType, _refClass.toClass().newInstance());
                }
            }
        }
        writeCtFile(cc);
        //
        return (Object) cc.toClass().newInstance();
    }

    private static void dynamicImplementsMethodsFromInterface(CtClass implementer, Method method)
            throws CannotCompileException {
        String methodCode = generateCode(method);
//		System.out.println(methodCode);
        CtMethod cm = CtNewMethod.make(methodCode, implementer);
        implementer.addMethod(cm);
    }

    private static String generateCode(Method method) {
        String methodName = method.getName();
        String methodReturnType = method.getReturnType().getCanonicalName();
        Class<?>[] parameters = method.getParameterTypes();
        Class<?>[] exceptionTypes = method.getExceptionTypes();
        StringBuffer exceptionBuffer = new StringBuffer();

        // 异常
        if (exceptionTypes.length > 0)
            exceptionBuffer.append(" throws ");
        for (int i = 0; i < exceptionTypes.length; i++) {
            if (i != exceptionTypes.length - 1)
                exceptionBuffer.append(exceptionTypes[i].getName()).append(",");
            else
                exceptionBuffer.append(exceptionTypes[i].getName());
        }

        StringBuffer parameterBuffer = new StringBuffer();
        // 组装方法的参数列表
        for (int i = 0; i < parameters.length; i++) {
            Class<?> parameter = parameters[i];
            String parameterType = parameter.getName();
            // 动态指定方法参数的变量名
            String refName = "a" + i;
            if (i != parameters.length - 1)
                parameterBuffer.append(parameterType).append(" " + refName)
                        .append(",");
            else
                parameterBuffer.append(parameterType).append(" " + refName);
        }

        StringBuffer methodDeclare = new StringBuffer();
        // 方法声明，由于是实现接口的方法，所以是public
        methodDeclare.append("public ").append(methodReturnType).append(" ")
                .append(methodName).append("(").append(parameterBuffer)
                .append(")").append(exceptionBuffer).append(" {\n");

        if (method.getReturnType().isPrimitive()) {
            if (method.getReturnType().equals(Void.TYPE)) {
                methodDeclare.append("return ;\n");
            } else if (method.getReturnType().equals(Boolean.TYPE))
                methodDeclare
                        .append("return false;\n");
            else if (method.getReturnType().equals(Integer.TYPE))
                methodDeclare
                        .append("return -1;\n");
            else if (method.getReturnType().equals(Long.TYPE))
                methodDeclare.append("return -1L;\n");
            else if (method.getReturnType().equals(Float.TYPE))
                methodDeclare
                        .append("return 0f;\n");
            else if (method.getReturnType().equals(Double.TYPE))
                methodDeclare
                        .append("return 0d;\n");
            else if (method.getReturnType().equals(Character.TYPE))
                methodDeclare
                        .append("return 'a';\n");
            else if (method.getReturnType().equals(Byte.TYPE))
                methodDeclare.append("return ((Byte)\"a\").byteValue();\n");
            else if (method.getReturnType().equals(Short.TYPE))
                methodDeclare
                        .append("return 0;\n");
            else {
                methodDeclare.append("return null;\n");
            }
        } else {
            methodDeclare.append("return null;\n");
        }

        methodDeclare.append("}");

        return methodDeclare.toString();
    }

    private static void writeCtFile(CtClass ctClass) {
        try {
            //ctClass.writeFile(new File(ProxyFactroy.class.getResource(".").getFile()).getAbsolutePath().replaceAll(packageName, ""));
            ctClass.writeFile(new File("/tmp/.bussservice").getAbsolutePath());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public ProxyFactory() {
        this(new HttpClientExchanger());
    }

    public ProxyFactory(Exchanger exchanger) {
        this(exchanger, null);
    }

    public ProxyFactory(Exchanger exchanger, Properties properties) {
        this.exchanger = exchanger;
        if (properties == null || properties.isEmpty()) {
            properties = new Properties();
            InputStream inputStream = ProxyFactory.class.getClassLoader().getResourceAsStream(DEFAULT_RESOURCE);
            try {
                properties.load(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        this.properties = properties;
    }
}
